package com.bjnet.camerarecord

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.camera2.*
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.view.Surface
import androidx.annotation.NonNull
import java.util.*

import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import android.media.MediaCodecList
import android.util.Size
import java.io.FileOutputStream
import java.lang.IllegalArgumentException
import java.nio.ByteBuffer
import java.util.concurrent.Executor
import kotlin.experimental.and


class OpenCameraAndEncode(private val context: Context) {

    companion object{
        val TAG = "OpenCameraAndEncode"
    }

    val write: Boolean = true
    fun init(surface: Surface): Boolean{
        this.tsurface = surface
        if(prepareMediaCodec()){
            if(write){
                fou = FileOutputStream("/sdcard/test.H265")
            }
            return prepareCamera()
        }
        return false
    }


    lateinit var backgroundThread: HandlerThread
    lateinit var backgroundHandler: Handler
    lateinit var captureRequestBuilder: CaptureRequest.Builder
    lateinit var cameraSession: CameraCaptureSession
    lateinit var cameraDevice :CameraDevice
    lateinit var tsurface: Surface

    @SuppressLint("MissingPermission")
    fun prepareCamera(): Boolean{
        backgroundThread = HandlerThread("Camera_back")
        backgroundThread.start()
        backgroundHandler = Handler(backgroundThread.looper)
        val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        val cameraID = manager.cameraIdList[0]
        manager.openCamera(cameraID, StateCallback(), backgroundHandler)
        return true
    }

    fun createPreview(){
        captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
//        captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
        captureRequestBuilder.addTarget(surface)
        cameraDevice.createCaptureSession(Collections.singletonList(surface), object : CameraCaptureSession.StateCallback(){
            override fun onConfigured(session: CameraCaptureSession) {
                Log.d(TAG, "onConfigured: ")
                cameraSession = session
                updatePreview()
            }

            override fun onConfigureFailed(session: CameraCaptureSession) {
                Log.e(TAG, "onConfigureFailed !!")
            }
        }, backgroundHandler)
    }

    private fun updatePreview() {
        try {
            cameraSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)
        } catch (e: CameraAccessException) {
            e.printStackTrace()
        }
    }

    inner class StateCallback: CameraDevice.StateCallback(){
        override fun onOpened(camera: CameraDevice) {
            Log.d(TAG, "onOpened:")
            cameraDevice = camera
            createPreview()
        }

        override fun onDisconnected(camera: CameraDevice) {
            Log.d(TAG, "onDisconnected !! ")
            cameraDevice.close()
        }

        override fun onError(camera: CameraDevice, error: Int) {
            Log.e(TAG, "onErrorCode : $error")
        }

    }

    private lateinit var mediaCodec: MediaCodec
    private lateinit var surface: Surface
    private val mimeType = MediaFormat.MIMETYPE_VIDEO_HEVC
    var head: ByteArray? = null

    lateinit var fou: FileOutputStream

    private fun prepareMediaCodec(): Boolean{
        try {
            val mediaFormat = MediaFormat.createVideoFormat(mimeType, 1920, 1080)
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 40_000)
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10)

            mediaCodec = MediaCodec.createEncoderByType(mimeType)
            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            surface = mediaCodec.createInputSurface()
            mediaCodec.setCallback(MediaCallback())
            mediaCodec.start()
        }catch (e: IllegalStateException){
            Log.e(TAG, "prepareMediaCodec: ", e )
            return false
        }catch (e: IllegalArgumentException){
            Log.e(TAG, "prepareMediaCodec: ", e )
        }
        return true
    }

    private fun encodeVideoAndSend(index: Int, info: MediaCodec.BufferInfo) {
        val encodedData: ByteBuffer? = mediaCodec.getOutputBuffer(index)
        if (encodedData != null) {
            var offset = 4
            if (encodedData.getInt(2) == 0x1) {
                offset = 3
            }
            val naluType: Int = encodedData.get(offset).and(0x7E).toInt().shr(1)
            if (head != null && 19 == naluType) {
                val bytes = ByteArray(info.size)
                encodedData.get(bytes)
                val newBuf = ByteArray(info.size + head!!.size)
                System.arraycopy(head, 0, newBuf, 0, head!!.size)
                System.arraycopy(bytes, 0, newBuf, head!!.size, bytes.size)

                if(write){
                    Log.d(TAG, "encodeVideoAndSend: " + newBuf.size)
                    fou.write(newBuf)
                }
            } else {
                val bytes = ByteArray(info.size)
                encodedData.get(bytes)

                if(write){
                    Log.d(TAG, "encodeVideoAndSend: " + bytes.size)
                    fou.write(bytes)
                }
            }
        }
    }

    fun resetOutputFormat(newFormat: MediaFormat) {
        val spsBuf = newFormat.getByteBuffer("csd-0")
        if (spsBuf != null) {
            this.head = ByteArray(spsBuf.remaining())
            spsBuf.get(this.head)
        }
    }

    inner class MediaCallback: MediaCodec.Callback(){
        override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {

        }

        override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
            encodeVideoAndSend(index, info)
            mediaCodec.releaseOutputBuffer(index, false)
        }

        override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
            Log.e(TAG, "onError: " , e )
        }

        override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
            Log.d(TAG, "onOutputFormatChanged: ")
            resetOutputFormat(format)
        }
    }

    fun close(): Boolean{
        Log.d(TAG, "Camera close !")
        cameraDevice.close()
        mediaCodec.stop()
        mediaCodec.release()

        if(write){
            fou.close()
        }
        return true
    }
}